﻿//----------------------------------------------------------------
// <copyright file="TeamFoundationFacade.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//----------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Security;
using System.Security.Principal;
using System.Text;
using System.Xml;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Server;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using MptCodeLibrary.Exceptions;

namespace MptCodeLibrary.TeamFoundation
{
    public class TeamFoundationFacade : ITeamFoundationFacade
    {
        /// <summary>
        /// Subscribes to the WorkItemChangedEvent for the defined TFS server
        /// </summary>
        /// <param name="serverName">Name of the TFS server.</param>
        /// <param name="receiverPath">Where the TFS event will be delivered to.</param>
        /// <param name="filterExpression">The filter expression, if any, to use for the event.
        /// An example would be filtering by team project.</param>
        public void SignupForReceivingEvent(string serverName, string receiverPath, string filterExpression)
        {
            TeamFoundationServer server = TeamFoundationServerFactory.GetServer(serverName);
            IEventService eventEndpoint = (IEventService)server.GetService(typeof(IEventService));

            DeliveryPreference delPreference = new DeliveryPreference();
            delPreference.Schedule = DeliverySchedule.Immediate;
            delPreference.Type = DeliveryType.Soap;
            delPreference.Address = receiverPath;

            // When/if this ClientID parameter is used by SubscribeEvent, then the made up sid
            // should throw an exception and alert the team to the fact that the parameter 
            // might now be used 
            try
            {
                eventEndpoint.SubscribeEvent("Sid is ignored", "WorkItemChangedEvent", filterExpression, delPreference);
            }
            catch (SecurityException)
            {
                throw new MptException(string.Format(CultureInfo.InvariantCulture,
                                                     "You do not have enough permissions on {0} to perform this action.  {1} needs to be in the administrator group for the TFS instance.",
                                                     serverName, WindowsIdentity.GetCurrent().Name));
            }
        }

        /// <summary>
        /// This method will look at the work item types for the projects and find the four WIT Categories
        /// {Value Prop, Feature group, feature, deliverable} that the code wants to receive change events for.
        /// </summary>
        /// <param name="serverNames">The TFS server names the events will be on.</param>
        /// <param name="projectNames">The project names the events care about.</param>
        /// <returns>
        /// Collection of unique Work Item Type names that will map to WIT categories
        /// </returns>
        public IEnumerable<string> GetEventWorkItemTypesForProjects(IEnumerable<string> serverNames, IEnumerable<string> projectNames)
        {
            List<string> workItemTypes = new List<string>();
            foreach (string tfsServer in serverNames)
            {
                WorkItemStore workItemStore = TheWorkItemStore(tfsServer);

                if (((ICollection<string>)projectNames).Count > 0)
                {
                    foreach (string projectName in projectNames)
                    {
                        if (!workItemStore.Projects.Contains(projectName)) continue;

                        Project project = workItemStore.Projects[projectName];

                        AddMptWorkItemTypesToListForProject(project, workItemTypes);
                    }
                }
                else
                {
                    foreach (Project project in workItemStore.Projects)
                        AddMptWorkItemTypesToListForProject(project, workItemTypes);
                }

            }

            if (workItemTypes.Count == 0)
                throw new MptException(
                    "There aren't any MPT work item types returned.  This is project is not supported by MPT.  Check your server and project configuration and try again.");

            return workItemTypes;
        }

        private static void AddMptWorkItemTypesToListForProject(Project project, ICollection<string> workItemTypes)
        {
            foreach (WorkItemType workItemType in project.WorkItemTypes)
            {
                if (!workItemType.FieldDefinitions.Contains("Microsoft.VSTS.Common.WITCategory")) continue;

                string wit = workItemType.FieldDefinitions["Microsoft.VSTS.Common.WITCategory"].AllowedValues[0];
                if (wit == "Value Proposition" || wit == "Feature Group" || wit == "Feature" || wit == "Deliverable")
                {
                    if (!workItemTypes.Contains(workItemType.Name))
                        workItemTypes.Add(workItemType.Name);
                }
            }
        }

        /// <summary>
        /// Gets the global lists from the server as a XDocument.
        /// </summary>
        /// <param name="serverName">Name of the TFS server.</param>
        public string GetGlobalLists(string serverName)
        {
            XmlDocument allGlobalLists = TheWorkItemStore(serverName).ExportGlobalLists();
            
            return allGlobalLists.InnerXml;
        }

        /// <summary>
        /// Adds the global list to the specified tfs server
        /// </summary>
        /// <param name="serverName">Name fo the TFS Server to add the list to.</param>
        /// <param name="updatedGlobalList">The XML string of the updated global list.</param>
        public void SetGlobalList(string serverName, string updatedGlobalList)
        {
            TheWorkItemStore(serverName).ImportGlobalLists(updatedGlobalList);
        }

        /// <summary>
        /// Gets the WIT for project for the type specified.
        /// </summary>
        /// <param name="serverName">Name of the TFS server.</param>
        /// <param name="projectName">Name of the project.</param>
        /// <param name="workItemType">WIT.</param>
        /// <returns>Xml string that represents the WIT</returns>
        public string GetWorkItemTypeForProject(string serverName, string projectName, string workItemType)
        {
            Project theProject = TheWorkItemStore(serverName).Projects[projectName];
            WorkItemType witToExport = theProject.WorkItemTypes[workItemType];

            XmlDocument wit = witToExport.Export(false);

            return wit.InnerXml;
        }

        /// <summary>
        /// Updates the WIT for project
        /// </summary>
        /// <param name="serverName">Name of the TFS server.</param>
        /// <param name="projectName">Name of the project.</param>
        /// <param name="workItemTypeXml">The full XML of the work item type to update</param>
        public void UpdateWorkItemTypeForProject(string serverName, string projectName, string workItemTypeXml)
        {
            Project theProject = TheWorkItemStore(serverName).Projects[projectName];
            WorkItemTypeCollection witCollection = theProject.WorkItemTypes;

            // Info:  I have discovered that this api contains some optimization logic where the new
            //        type being imported is compared against the local cache of the type.  If there
            //        is no difference then the call ends up being a no-op (i.e. the call is not
            //        forwarded on to the server).
            witCollection.Import(workItemTypeXml);
        }

        /// <summary>
        /// Retrieves the work items that have changed on the tfs server for the projects in the last 48 hours
        /// </summary>
        /// <param name="serverName">Name of the TFS server.</param>
        /// <param name="projectsToCheck">The projects that are being monitored on the TFS server.  An empty string means all projects on the server</param>
        /// <returns>
        /// Collection of work items that have changed in the last 48 hours
        /// </returns>
        public IEnumerable<WorkItemSummary> ChangedWorkItems(string serverName, IEnumerable<string> projectsToCheck)
        {

            string wiql = @"SELECT ID, Title FROM WorkItems 
                            WHERE [Microsoft.VSTS.Common.WITCategory] IN ('Value Proposition', 'Feature Group', 'Feature', 'Deliverable')
                                AND System.ChangedDate > '"
                          + DateTime.Now.AddHours(-48).ToLongDateString() + "'"
                          + CreateProjectNameWhereClause((ICollection<string>)projectsToCheck);

            List<WorkItemSummary> workItemSummaries = new List<WorkItemSummary>();
            foreach (WorkItem workItem in TheWorkItemStore(serverName).Query(wiql))
            {
                WorkItemSummary workItemSummary = ConvertWorkItemToSummary(workItem);
                if (workItemSummary != null)
                    workItemSummaries.Add(workItemSummary);
            }

            return workItemSummaries;
        }

        /// <summary>
        /// Gets the work item based on the System.Id.
        /// </summary>
        /// <param name="serverName">Name of the TFS server.</param>
        /// <param name="workItemId">The work item id.</param>
        /// <returns></returns>
        public WorkItemSummary GetWorkItem(string serverName, int workItemId)
        {
            return ConvertWorkItemToSummary(TheWorkItemStore(serverName).GetWorkItem(workItemId));
        }

        private static string CreateProjectNameWhereClause(ICollection<string> projectsToCheck)
        {
            if (projectsToCheck.Count > 0)
                return string.Format(CultureInfo.InvariantCulture, " AND [System.TeamProject] IN ({0})",
                                 CreateCommaDelimitedListOfProjectNames(projectsToCheck));
            
            return string.Empty;
        }

        private static string CreateCommaDelimitedListOfProjectNames(IEnumerable<string> projectsToCheck)
        {
            StringBuilder sb = new StringBuilder();
            foreach (string project in projectsToCheck)
            {
                if (sb.Length > 0)
                    sb.Append(", ");

                sb.Append(string.Format(CultureInfo.InvariantCulture, " '{0}'", project));
            }

            return sb.ToString();
        }

        /// <summary>
        /// Gets the work items for the project of the type specified.
        /// </summary>
        /// <param name="serverName">Name of the TFS server.</param>
        /// <param name="projectName">Name of the project to get the work items for.</param>
        /// <param name="workItemType">Type of the work items to retrieve.</param>
        /// <returns>
        /// Collection of work items for the provided type
        /// </returns>
        public IEnumerable<WorkItemSummary> GetWorkItemsForProjectOfType(string serverName, string projectName, 
            string workItemType)
        {
            const string wiql = @"SELECT ID, Title FROM WorkItems WHERE [Team Project] = 
                            @projectName AND [Microsoft.VSTS.Common.WITCategory] = @workItemType";
            Dictionary<string, string> queryParams = new Dictionary<string, string>();
            queryParams.Add("projectName", projectName);
            queryParams.Add("workItemType", workItemType);

            List<WorkItemSummary> workItemSummaries = new List<WorkItemSummary>();
            foreach (WorkItem workItem in TheWorkItemStore(serverName).Query(wiql, queryParams))
            {
                workItemSummaries.Add(ConvertWorkItemToSummary(workItem));
            }

            return workItemSummaries;
        }

        /// <summary>
        /// The WorkItem could be from a process template that doesn't support MPT, if that's the case
        /// then the WITCategory field won't be a field in the work item.  When this occurs return null
        /// because we don't want to deal with it.
        /// </summary>
        private static WorkItemSummary ConvertWorkItemToSummary(WorkItem workItem)
        {
            if (workItem != null && workItem.Fields.Contains("Microsoft.VSTS.Common.WITCategory"))
            {
                return new WorkItemSummary(workItem.Id, workItem.Title, workItem.Project.Name,
                                           (string) workItem.Fields["Microsoft.VSTS.Common.WITCategory"].Value);
            }

            return null;
        }

        /// <summary>
        /// Updates the existing work items for the project that have the ParentShortName field set to existingParentShortName
        /// with newParentShortName
        /// </summary>
        /// <param name="serverName">Name of the TFS server.</param>
        /// <param name="projectName">Name of the Team Project.</param>
        /// <param name="existingParentShortName">The exisitng parent short name.</param>
        /// <param name="newParentShortName">The new shart name of the parent.</param>
        public void UpdateExistingWorkItemsWithNewParent(string serverName, string projectName, 
            string existingParentShortName, string newParentShortName)
        {
            string wiq = string.Format(CultureInfo.InvariantCulture,
                                     @"SELECT System.Id 
                                      FROM WorkItems 
                                      WHERE [System.TeamProject] = '{0}'
                                        AND [Microsoft.VSTS.Common.ParentShortName] = '{1}'",
                                       projectName, existingParentShortName);

            WorkItemStore workItemStore = TheWorkItemStore(serverName);
            
            WorkItemCollection workItemCollection = workItemStore.Query(wiq);

            if (workItemCollection.Count <= 0) return;

            WorkItem[] updatedWorkItems = new WorkItem[workItemCollection.Count];
            int index = 0;
            foreach (WorkItem wi in workItemCollection)
            {
                WorkItem mostRecent = workItemStore.GetWorkItem(wi.Id);
                mostRecent.Fields["Microsoft.VSTS.Common.ParentShortName"].Value = newParentShortName;
                updatedWorkItems[index++] = mostRecent;
            }
            workItemStore.BatchSave(updatedWorkItems);
        }

        private static WorkItemStore TheWorkItemStore(string serverName)
        {
            TeamFoundationServer tfs = TeamFoundationServerFactory.GetServer(serverName);

            return (WorkItemStore)tfs.GetService(typeof(WorkItemStore));
        }

        public bool ProjectExists(string serverName, string teamProjectName)
        {
            return TheWorkItemStore(serverName).Projects.Contains(teamProjectName);
        }
    }
}